package com.weic.easycache.task;

import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;

import com.google.gson.Gson;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.UpdateBuilder;
import com.weic.easycache.access.NetAcesHelper;
import com.weic.easycache.exception.ParamLackedException;
import com.weic.easycache.framework.Errors;
import com.weic.easycache.framework.KeyUtils;
import com.weic.easycache.framework.biz.IApiBiz;
import com.weic.easycache.framework.db.ApiCacheTable;
import com.weic.easycache.framework.json.JsonBase;
import com.weic.easycache.framework.web.Api;
import com.weic.easycache.utils.JsonUtils;

/**从网络更新数据。根据缓存设置，可能会先加载数据库数据并post到UI，然后从网络更新再刷新UI**/
public class ApiTask extends AbsTask {
	private final Dao<ApiCacheTable, String> mDao;
	private final Api<?> mApi;
	private final boolean mLastUsed;
	private final Map<String, String> mParams;
	private final IApiBiz<?> mBiz;
	private boolean mNetRefresh;
	private final boolean mNeedful;
	//private final boolean mCanIrgnore;

	private ApiCacheTable mData;

	public ApiTask(Context context, int id, IApiBiz<?> biz, boolean canIrgnore, Api<?> api, Map<String, String> params,
			Dao<ApiCacheTable, String> dao, boolean lastUsed, boolean netRefresh, boolean needful) {
		super(context, id, biz, canIrgnore);
		mBiz = biz;
		//mCanIrgnore = canIrgnore;
		mApi = api;
		mParams = params;
		mDao = dao;
		mLastUsed = lastUsed;
		mNetRefresh = netRefresh;
		mNeedful = needful;
	}

	@Override
	protected boolean doSomething() throws Exception {
		if(!isExcuteAgain()) {
			mData = null;
			/*try {
				updateParams();
			} catch(ParamLackedException e) {
				e.printStackTrace();
				if(mCanIrgnore) {
					return true;
				}else {
					throw e;
				}
			}*/
			//已经在父类作了忽略任务的处理
			updateParams();
		}

		if(mLastUsed) {	//忽略网络操作
			if(mApi.cacheTimeMS >= 0) {
				readFromDB();
				if(!deliverResult()) {
					deleteDBCache();
					return false;
				}
				return true;
			}else {
				setErrorCode(Errors.NO_CACHE, ERROR_NO);
				return false;
			}
		}else {
			if(mNetRefresh) {	//显式更新
				//忽略库操作，直接读网络
				if(isNetConnected()) {
					if(readFromNet()) {
						if(!deliverResult()) {
							deleteDBCache();
							return false;
						}
						return true;
					}else {
						setErrorCode(Errors.NO_DATA, ERROR_NO);
						return false;
					}
				}else {
					setErrorCode(Errors.NEED_NET, ERROR_NO);
					return false;
				}
			}else {
				if(mApi.cacheTimeMS > 0 && readFromDB()) {	//不能cacheTimeMS >= 0，等于0为可缓存，但进行数据请求时必须忽略
					if(isOutOfDate() && isNetConnected()) {
						/* 是否后台更新。对于最后一个任务，直接启用后台更新；
						 * 若不是最后一个任务，由于该数据对后面task有影响，应该询问。*/
						if(isLastTask() || mBiz.canBgUpdate(mId)) {
							mNetRefresh = true;
							//启动后台线程更新数据
							queueBgTask(this);
						}else {
							//丢弃数据，以回收内存
							//freeData();
							//若没有读取成功，则已经从库里面读取成功，因此也要返回true
							readFromNet();
						}
					}
					if(!deliverResult()) {
						deleteDBCache();
						return false;
					}
					return true;
				}else {
					//从网络读取
					if(isNetConnected()) {
						if(!mNeedful && mBiz.canBgUpdate(mId)) {
							mNetRefresh = true;
							queueBgTask(this);
							//这里没有数据可以deliver
							return true;
						}
						if(readFromNet()) {
							if(!deliverResult()) {
								deleteDBCache();
								return false;
							}
							return true;
						}else {
							setErrorCode(Errors.NO_DATA, ERROR_NO);
							return false;
						}
					}else {
						setErrorCode(Errors.NEED_NET, ERROR_NO);
						return false;
					}
				}
			}
		}
	}

	private boolean readFromDB() throws Exception {
		readFromNet = false;
		if(mLastUsed) {
			QueryBuilder<ApiCacheTable, String> queryBuilder = mDao.queryBuilder();
			queryBuilder.where().eq(ApiCacheTable.FIELD_CATEGORY, getDBCategory());
			queryBuilder.orderBy(ApiCacheTable.FIELD_DATE, false);
			mData = queryBuilder.queryForFirst();
		}else {
			mData = mDao.queryForId(getDBCacheKey());
		}
		if(mData != null) {
			return true;
		}
		return false;
	}

	private boolean readFromNet = false;
	private boolean readFromNet() throws Exception {
		readFromNet = true;
		String json = null;
		if(mApi.pagination) {
			if(getPageNo() == 1 || hasThisPage()) {
				json = NetAcesHelper.getDefault().executeAccess(mApi.baseUrl, mApi.controller, mApi.method, getHeaders(),
						mApi.params, getParamValues());
			}
			if(json != null && json.length() > 0) {
				mData = new ApiCacheTable();
				mData.value = json;
				mData.recordCount = getRecordCount();
				if(mApi.cacheTimeMS >= 0) cacheToDB();
				return true;
			}
		}else {
			json = NetAcesHelper.getDefault().executeAccess(mApi.baseUrl, mApi.controller, mApi.method, getHeaders(),
					mApi.params, getParamValues());
			if(json != null && json.length() > 0) {
				mData = new ApiCacheTable();
				mData.value = json;
				mData.recordCount = -1;
				if(mApi.cacheTimeMS >= 0) cacheToDB();
				return true;
			}
		}
		return false;
	}

	private Map<String, String> mHeaders = null;
	private Map<String, String> getHeaders() {
		if(mHeaders == null) {
			if(mApi.headers != null && mApi.headers.size() > 0) {
				mHeaders = new HashMap<String, String>();
				mHeaders.putAll(mApi.headers);
			}
			if(mApi.passport) {
				if(mHeaders == null) mHeaders = new HashMap<String, String>();
				mHeaders.put(Api.PASSPORT, mParams.get(Api.PASSPORT));
			}
		}
		return mHeaders;
	}

	private int recordCount = -1;
	private boolean netReaded = false;
	private int getRecordCount() throws Exception {
		if(recordCount < 0 || (!netReaded && readFromNet)) {	//如果第一次从DB读，然后又从Net，则需要再次重新请求
			if(readFromNet) {
				String[][] param$values = getRecordCountParamAndValues();
				String json = NetAcesHelper.getDefault().executeAccess(mApi.baseUrl, mApi.controller, mApi.method, getHeaders(),
						param$values[0], param$values[1]);
				recordCount = JsonUtils.parseRecordCount(json);
				//检查是否与库中一致
				if(mApi.cacheTimeMS >= 0 && recordCount > 0 && getPageNo() > 1) {
					int dbrecount = getDBRecordCount();
					if(dbrecount > 0 && dbrecount != recordCount) {	//有变化，置为已过期
						updataDBRecordCountAndSetOldToOutOfDate(recordCount);
					}
				}
				netReaded = true;
			}else {
				recordCount = mData.recordCount;
			}
		}
		if(recordCount < 0) recordCount = 0;	//已经正确返回，不能抛异常
		return recordCount;
	}

	private int getDBRecordCount() {
		int count = -1;
		try {
			QueryBuilder<ApiCacheTable, String> queryBuilder = mDao.queryBuilder();
			queryBuilder.where().eq(ApiCacheTable.FIELD_CATEGORY, getDBCategory());
			queryBuilder.selectColumns(ApiCacheTable.FIELD_RECORD_COUNT);
			queryBuilder.orderBy(ApiCacheTable.FIELD_DATE, false);
			ApiCacheTable table = queryBuilder.queryForFirst();
			count = table!=null ? table.recordCount : -1;
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return count;
	}

	private int pageSize = 0;
	private int getPageSize() {	//不会大于Api.PAGE_SIZE_MAX，已经在updateParams()中检查过
		if(pageSize <= 0) pageSize = Integer.parseInt(mParams.get(Api.PAGE_SIZE));
		return pageSize;
	}

	private int pageNo = 0;
	private int getPageNo() {	//不会小于1，已经在updateParams()中检查过
		if(pageNo < 1) pageNo = Integer.parseInt(mParams.get(Api.PAGE_NO));
		return pageNo;
	}

	private boolean hasThisPage() throws Exception {
		return getRecordCount() > (getPageNo()-1) * getPageSize();
	}

	private boolean hasMorePage() throws Exception {
		return getRecordCount() > getPageNo() * getPageSize();
	}

	private boolean deliverResult() throws Exception {
		if(mData != null) {
			Gson gson = new Gson();
			JsonBase<?> jsonObj = gson.fromJson(mData.value, mApi.typeToken.getType());
			String message = mBiz.update(mId, jsonObj, isExcuteAgain());
			if(message == null && mApi.pagination) {
				mBiz.updatePageInfo(mId, getPageNo(), getRecordCount(), hasMorePage());
			}
			if(message == null) {	//IBiz.RESULT_OK
				return true;
			}else {
				if(message.length() > 0) setMessage(null, null, message);	//IBiz.RESULT_FAILURE
				return false;
			}
		}
		return false;
	}

	private void updataDBRecordCountAndSetOldToOutOfDate(int newRecordCount) {
		try {
			UpdateBuilder<ApiCacheTable, String> updateBuilder = mDao.updateBuilder();
			updateBuilder.where().eq(ApiCacheTable.FIELD_CATEGORY, getDBCategory());
			updateBuilder.updateColumnValue(ApiCacheTable.FIELD_RECORD_COUNT, newRecordCount);
			updateBuilder.updateColumnValue(ApiCacheTable.FIELD_DATE, new Date(0));
			updateBuilder.update();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private void cacheToDB() throws Exception {
		mData.key = getDBCacheKey();
		mData.category = getDBCategory();
		mData.date = new Date();
		mDao.createOrUpdate(mData);
	}
	
	private void deleteDBCache() {
		if(mApi.cacheTimeMS >= 0) {
			try {
				mDao.deleteById(getDBCacheKey());
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	private String dBCacheKey = null;
	private String getDBCacheKey() {
		if(dBCacheKey == null) dBCacheKey = KeyUtils.makeDBCacheKey(mApi, mParams);
		return dBCacheKey;
	}
	
	private String dBCategory;
	private String getDBCategory() {
		if(dBCategory == null) dBCategory = KeyUtils.getCategory(mApi);
		return dBCategory;
	}
	
	private boolean isOutOfDate() {
		return mData.date.getTime() + mApi.cacheTimeMS < System.currentTimeMillis();
	}

	private void updateParams() {
		mBiz.fetchParamValues(mId, mParams);
		String value;
		if(mApi.params != null) {
			for(String key : mApi.params) {
				value = mParams.get(key);
				if(value != null && value.length() > 0) {
					if(key.equals(Api.PAGE_SIZE) && Integer.parseInt(value) > Api.PAGE_SIZE_MAX) {
						throw new ParamLackedException("pageSize 不能超过 " + Api.PAGE_SIZE_MAX);
					}
					if(key.equals(Api.PAGE_NO) && Integer.parseInt(value) < 1) {
						throw new ParamLackedException("pageNo 不能小于 1");
					}
				}else throw new ParamLackedException("参数 " + key + " 值不能为空");
			}
		}
		if(mApi.passport) {
			value = mParams.get(Api.PASSPORT);
			if(value == null || value.length() == 0) throw new ParamLackedException("参数 " + Api.PASSPORT + " 值不能为空");
		}
		if(mApi.cacheParams != null) {
			for(String key : mApi.cacheParams) {
				value = mParams.get(key);
				if(value == null || value.length() == 0) throw new ParamLackedException("参数 " + key + " 值不能为空");
			}
		}
	}

	private String[][] getRecordCountParamAndValues() {
		String[][] result = new String[2][mApi.params.length-2];
		int i = 0;
		for (String key : mApi.params) {
			if(!key.equals(Api.PAGE_NO) && !key.equals(Api.PAGE_SIZE)) {
				result[0][i] = key;
				result[1][i] = mParams.get(key);
				i++;
			}
		}
		return result;
	}

	private String[] getParamValues() {
		String[] values = null;
		if(mApi.params != null) {
			int len = mApi.params.length;
			values = new String[len];
			for(int i=0; i < len; i++) {
				values[i] = mParams.get(mApi.params[i]);
			}
		}
		return values;
	}

	/* 为了保证只向Biz deliver一次数据，有时虽然从库里读取成功，但是并没有立即deliverResult，而是看是否有网络更新操作，若有
	 * 也不一定成功，若失败了，则deliverResult从库里面查询的数据，因此不能在进行网络操作的时候删除旧数据。*/
	/*private void freeData() {
		mData.key = null;
		mData.value = null;
		mData.category = null;
		mData.date = null;
		mData = null;
	}*/
}
